using System;
using System.Data;

namespace ODX.Core
{
    /// <summary>
    /// Marks an <see cref="Entity"/> subclass to write it's properties to the specified table.
    /// </summary>
    public class TableAttribute : Attribute
    {
        private string name = null;
        private bool versioned = false;

        /// <summary>
        /// Creates an attribute and assumes that name of marked class is the same as data table name.
        /// </summary>
        public TableAttribute() { }

        /// <summary>
        /// Creates an attribute and let user to specify data table name to map properties to.
        /// </summary>
        /// <param name="name">data table name.</param>
        public TableAttribute(string name)
        {
            this.name = name;
        }

        /// <summary>Data table name.</summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        /// <summary>
        /// If set to true forces Session to use RowVersion column to track concurrent changes.
        /// </summary>
        public bool Versioned
        {
            get { return versioned; }
            set { versioned = value; }
        }
    }


    /// <summary>
    /// Lets the developer define a marker or a type factory for a <see cref="Entity"/> subclasses hierarchy.
    /// </summary>
    public class TypeDefAttribute : Attribute
    {
        private IEntityFactory factory;
        private string typeDefKey;

        /// <summary>
        /// Creates an attribute and sets the type factory.
        /// The type of factory should implement a <see cref="IEntityFactory"/> interface
        /// and contain a parameterless constructor.
        /// </summary>
        /// <param name="factory">Type of the factory class.</param>
        public TypeDefAttribute(Type factory)
        {
            this.factory = (IEntityFactory)Activator.CreateInstance(factory);
        }

        /// <summary>
        /// Creates an attribute and set a short string marker for the underlying type.
        /// this marker will be written into TypeDef field into underlying <see cref="DataTable"/>
        /// </summary>
        /// <param name="typeDefKey">Type marker for the underlying class</param>
        public TypeDefAttribute(string typeDefKey)
        {
            this.typeDefKey = typeDefKey;
            factory = new TypeDefFactory(typeDefKey);
        }

        /// <summary>
        /// Factory which is used to determine type of entity to wrap particular <see cref="DataRow"/>
        /// </summary>
        public IEntityFactory Factory { get { return factory; } }

        /// <summary>
        /// Short string marker for the underlying <see cref="Entity"/> type.
        /// </summary>
        public string Key { get { return typeDefKey; } }
    }

    /// <summary>
    /// Sets column attributes: mapping name, uniqueness, ...
    /// </summary>
    public class ColumnAttribute : Attribute
    {
        private string name = null;
        private bool unique = false;
        private int maxLength = 50;
        private int scale = -1;
        private string dbType;


        /// <summary>
        /// Creates an attribute and assumes that column name is the same as field name.
        /// For the parent reference foreign key field name is build as field name + "ID".
        /// </summary>
        public ColumnAttribute(){}

        /// <summary>
        /// Creates an attribute and sets explicit mapping to the data source column name.
        /// </summary>
        /// <param name="name">Data source column name.</param>
        public ColumnAttribute(string name)
        { this.name = name; }

        /// <summary>
        /// Data source column name.
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        /// <summary>
        /// If true forces ODX to create a Unique constraint for this column.
        /// </summary>
        public bool Unique
        {
            get { return unique; }
            set { unique = value; }
        }

        /// <summary>
        /// Maximum length of the column value (if applicable for the column type)
        /// </summary>
        public int MaxLength
        {
            get { return maxLength; }
            set { maxLength = value; }
        }

        /// <summary>
        /// Sets the provider-specific data type for the column.
        /// </summary>
        public string DbType
        {
            get { return dbType; }
            set { dbType = value; }
        }

        /// <summary>
        /// Scale factor for numeric data.
        /// </summary>
        public int Scale
        {
            get { return scale; }
            set { scale = value; }
        }
    }

    /// <summary>
    /// This attributes allows developer to explicitly set data table for the foreign key in 1:N relation.
    /// This can be used to move foreign key along the entity hierarchy
    /// as it is recommended to create FKs int "most parent" table.
    /// </summary>
    public class RelatedTableAttribute : Attribute
    {
        private string name = null;

        /// <summary>
        /// Creates attribute and sets the table for the FK to be created into.
        /// </summary>
        /// <param name="name">Table name for the FK to be created into.</param>
        public RelatedTableAttribute(string name)
        {
            this.name = name;
        }

        /// <summary>
        /// Table name for the FK to be created into.
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
    }

    /// <summary>
    /// This attribute let developer to specify link table name for N:M relation.
    /// </summary>
    public class LinkTableAttribute : Attribute
    {
        private string parentColumn;
        private string childColumn;
        private string name;

        /// <summary>
        /// Creates an attribute and sets table name to create links into.
        /// </summary>
        /// <param name="name">Table name to create links into.</param>
        public LinkTableAttribute(string name)
        {
            this.name = name;
        }

        /// <summary>Table name to create links into.</summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        /// <summary>
        /// Name of the column in the link table which is the FK to the table of the class the collection marked by the attribute belongs to.
        /// </summary>
        public string ParentColumn
        {
            get { return parentColumn; }
            set { parentColumn = value; }
        }

        /// <summary>
        /// Name of the column in the link table which is FK to the table of the objects stored in the attributed collection.
        /// </summary>
        public string ChildColumn
        {
            get { return childColumn; }
            set { childColumn = value; }
        }
    }

    /// <summary>
    /// When attached to a class forces ODX not to generate proxy class.
    /// </summary>
    public class ManualAttribute : Attribute { }

    /// <summary>
    /// This attribute tells ODX that the class is the root class in the hierarchy.
    /// In other words this attribute forces ODX to create TypeDef column in underlying table.
    /// this column will contain short string markers for each class in the hierarchy.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class)]
    public class HierarchyRootAttribute : Attribute { }

    /// <summary>
    /// this attribute allows developer create multicolumn unique indexes.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class UniqueConstraintAttribute : Attribute
    {
        private readonly string[] columns;

        /// <summary>
        /// Creates an attrinbute and forces ODX to create unique index in the data chema.
        /// </summary>
        /// <param name="columns">List of the unique index columns.</param>
        public UniqueConstraintAttribute(params string[] columns)
        {
            this.columns = columns;
        }

        /// <summary>List of the unique index columns.</summary>
        public string[] Columns { get { return columns; } }
    }
}
